/*
 * ! Ext JS Library 3.0.0 Copyright(c) 2006-2009 Ext JS, LLC licensing@extjs.com
 * http://www.extjs.com/license
 */
Ext.ns('Ext.ux.grid');

/**
 * @class Ext.ux.grid.RowEditor
 * @extends Ext.Panel Plugin (ptype = 'roweditor') that adds the ability to
 *          rapidly edit full rows in a grid. A validation mode may be enabled
 *          which uses AnchorTips to notify the user of all validation errors at
 *          once.
 * 
 * @ptype roweditor
 */
Ext.ux.grid.RowEditor = Ext.extend(Ext.Panel, {
	floating : true,
	shadow : false,
	layout : 'hbox',
	cls : 'x-small-editor',
	buttonAlign : 'center',
	baseCls : 'x-row-editor',
	elements : 'header,footer,body',
	frameWidth : 5,
	buttonPad : 3,
	clicksToEdit : 'auto',
	monitorValid : true,
	focusDelay : 250,
	errorSummary : true,

	defaults : {
		normalWidth : true
	},

	initComponent : function() {
		Ext.ux.grid.RowEditor.superclass.initComponent.call(this);
		this.addEvents(
				/**
				 * @event beforeedit Fired before the row editor is activated.
				 *        If the listener returns <tt>false</tt> the editor
				 *        will not be activated.
				 * @param {Ext.ux.grid.RowEditor}
				 *            roweditor This object
				 * @param {Number}
				 *            rowIndex The rowIndex of the row just edited
				 */
				'beforeedit',
				/**
				 * @event validateedit Fired after a row is edited and passes
				 *        validation. If the listener returns <tt>false</tt>
				 *        changes to the record will not be set.
				 * @param {Ext.ux.grid.RowEditor}
				 *            roweditor This object
				 * @param {Object}
				 *            changes Object with changes made to the record.
				 * @param {Ext.data.Record}
				 *            r The Record that was edited.
				 * @param {Number}
				 *            rowIndex The rowIndex of the row just edited
				 */
				'validateedit',
				/**
				 * @event afteredit Fired after a row is edited and passes
				 *        validation. This event is fired after the store's
				 *        update event is fired with this edit.
				 * @param {Ext.ux.grid.RowEditor}
				 *            roweditor This object
				 * @param {Object}
				 *            changes Object with changes made to the record.
				 * @param {Ext.data.Record}
				 *            r The Record that was edited.
				 * @param {Number}
				 *            rowIndex The rowIndex of the row just edited
				 */
				'afteredit');
	},

	init : function(grid) {
		this.grid = grid;
		this.ownerCt = grid;
		if (this.clicksToEdit === 2) {
			grid.on('rowdblclick', this.onRowDblClick, this);
		} else {
			grid.on('rowclick', this.onRowClick, this);
			if (Ext.isIE) {
				grid.on('rowdblclick', this.onRowDblClick, this);
			}
		}

		// stopEditing without saving when a record is removed from Store.
		grid.getStore().on('remove', function() {
					this.stopEditing(false);
				}, this);

		grid.on({
					scope : this,
					keydown : this.onGridKey,
					columnresize : this.verifyLayout,
					columnmove : this.refreshFields,
					reconfigure : this.refreshFields,
					destroy : this.destroy,
					bodyscroll : {
						buffer : 250,
						fn : this.positionButtons
					}
				});
		grid.getColumnModel().on('hiddenchange', this.verifyLayout, this, {
					delay : 1
				});
		grid.getView().on('refresh', this.stopEditing.createDelegate(this, []));
	},

	refreshFields : function() {
		this.initFields();
		this.verifyLayout();
	},

	isDirty : function() {
		var dirty;
		this.items.each(function(f) {
					if (String(this.values[f.id]) !== String(f.getValue())) {
						dirty = true;
						return false;
					}
				}, this);
		return dirty;
	},

	startEditing : function(rowIndex, doFocus) {
		if (this.editing && this.isDirty()) {
			this.showTooltip('You need to commit or cancel your changes');
			return;
		}
		this.editing = true;
		if (typeof rowIndex == 'object') {
			rowIndex = this.grid.getStore().indexOf(rowIndex);
		}
		if (this.fireEvent('beforeedit', this, rowIndex) !== false) {
			var g = this.grid, view = g.getView();
			var row = view.getRow(rowIndex);
			var record = g.store.getAt(rowIndex);
			this.record = record;
			this.rowIndex = rowIndex;
			this.values = {};
			if (!this.rendered) {
				this.render(view.getEditorParent());
			}
			var w = Ext.fly(row).getWidth();
			this.setSize(w);
			if (!this.initialized) {
				this.initFields();
			}
			var cm = g.getColumnModel(), fields = this.items.items, f, val;
			for (var i = 0, len = cm.getColumnCount(); i < len; i++) {
				val = this.preEditValue(record, cm.getDataIndex(i));
				f = fields[i];
				f.setValue(val);
				this.values[f.id] = val || '';
			}
			this.verifyLayout(true);
			if (!this.isVisible()) {
				this.setPagePosition(Ext.fly(row).getXY());
			} else {
				this.el.setXY(Ext.fly(row).getXY(), {
							duration : 0.15
						});
			}
			if (!this.isVisible()) {
				this.show().doLayout();
			}
			if (doFocus !== false) {
				this.doFocus.defer(this.focusDelay, this);
			}
		}
	},

	stopEditing : function(saveChanges) {
		this.editing = false;
		if (!this.isVisible()) {
			return;
		}
		if (saveChanges === false || !this.isValid()) {
			this.hide();
			return;
		}
		var changes = {}, r = this.record, hasChange = false;
		var cm = this.grid.colModel, fields = this.items.items;
		for (var i = 0, len = cm.getColumnCount(); i < len; i++) {
			if (!cm.isHidden(i)) {
				var dindex = cm.getDataIndex(i);
				if (!Ext.isEmpty(dindex)) {
					var oldValue = r.data[dindex];
					var value = this.postEditValue(fields[i].getValue(),
							oldValue, r, dindex);
					if (String(oldValue) !== String(value)) {
						changes[dindex] = value;
						hasChange = true;
					}
				}
			}
		}
		if (hasChange
				&& this.fireEvent('validateedit', this, changes, r,
						this.rowIndex) !== false) {
			r.beginEdit();
			for (var k in changes) {
				if (changes.hasOwnProperty(k)) {
					r.set(k, changes[k]);
				}
			}
			r.endEdit();
			this.fireEvent('afteredit', this, changes, r, this.rowIndex);
		}
		this.hide();
	},

	verifyLayout : function(force) {
		if (this.el && (this.isVisible() || force === true)) {
			var row = this.grid.getView().getRow(this.rowIndex);
			this.setSize(Ext.fly(row).getWidth(), Ext.isIE ? Ext.fly(row)
							.getHeight()
							+ (Ext.isBorderBox ? 9 : 0) : undefined);
			var cm = this.grid.colModel, fields = this.items.items;
			for (var i = 0, len = cm.getColumnCount(); i < len; i++) {
				if (!cm.isHidden(i)) {
					var adjust = 0;
					if (i === 0) {
						adjust += 0; // outer padding
					}
					if (i === (len - 1)) {
						adjust += 3; // outer padding
					} else {
						adjust += 1;
					}
					fields[i].show();
					fields[i].setWidth(cm.getColumnWidth(i) - adjust);
				} else {
					fields[i].hide();
				}
			}
			this.doLayout();
			this.positionButtons();
		}
	},

	slideHide : function() {
		this.hide();
	},

	initFields : function() {
		var cm = this.grid.getColumnModel(), pm = Ext.layout.ContainerLayout.prototype.parseMargins;
		this.removeAll(false);
		for (var i = 0, len = cm.getColumnCount(); i < len; i++) {
			var c = cm.getColumnAt(i);
			var ed = c.getEditor();
			if (!ed) {
				ed = c.displayEditor || new Ext.form.DisplayField();
			}
			if (i == 0) {
				ed.margins = pm('0 1 2 1');
			} else if (i == len - 1) {
				ed.margins = pm('0 0 2 1');
			} else {
				ed.margins = pm('0 1 2');
			}
			ed.setWidth(cm.getColumnWidth(i));
			ed.column = c;
			if (ed.ownerCt !== this) {
				ed.on('focus', this.ensureVisible, this);
				ed.on('specialkey', this.onKey, this);
			}
			this.insert(i, ed);
		}
		this.initialized = true;
	},

	onKey : function(f, e) {
		if (e.getKey() === e.ENTER) {
			this.stopEditing(true);
			e.stopPropagation();
		}
	},

	onGridKey : function(e) {
		if (e.getKey() === e.ENTER && !this.isVisible()) {
			var r = this.grid.getSelectionModel().getSelected();
			if (r) {
				var index = this.grid.store.indexOf(r);
				this.startEditing(index);
				e.stopPropagation();
			}
		}
	},

	ensureVisible : function(editor) {
		if (this.isVisible()) {
			this.grid.getView().ensureVisible(this.rowIndex,
					this.grid.colModel.getIndexById(editor.column.id), true);
		}
	},

	onRowClick : function(g, rowIndex, e) {
		if (this.clicksToEdit == 'auto') {
			var li = this.lastClickIndex;
			this.lastClickIndex = rowIndex;
			if (li != rowIndex && !this.isVisible()) {
				return;
			}
		}
		this.startEditing(rowIndex, false);
		this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
	},

	onRowDblClick : function(g, rowIndex, e) {
		this.startEditing(rowIndex, false);
		this.doFocus.defer(this.focusDelay, this, [e.getPoint()]);
	},

	onRender : function() {
		Ext.ux.grid.RowEditor.superclass.onRender.apply(this, arguments);
		this.el.swallowEvent(['keydown', 'keyup', 'keypress']);
		this.btns = new Ext.Panel({
					baseCls : 'x-plain',
					cls : 'x-btns',
					elements : 'body',
					layout : 'table',
					width : (this.minButtonWidth * 2) + (this.frameWidth * 2)
							+ (this.buttonPad * 4), // width must be specified
					// for IE
					items : [{
								ref : 'saveBtn',
								itemId : 'saveBtn',
								xtype : 'button',
								text : this.saveText || 'Save',
								width : this.minButtonWidth,
								handler : this.stopEditing.createDelegate(this,
										[true])
							}, {
								xtype : 'button',
								text : this.cancelText || 'Cancel',
								width : this.minButtonWidth,
								handler : this.stopEditing.createDelegate(this,
										[false])
							}]
				});
		this.btns.render(this.bwrap);
	},

	afterRender : function() {
		Ext.ux.grid.RowEditor.superclass.afterRender.apply(this, arguments);
		this.positionButtons();
		if (this.monitorValid) {
			this.startMonitoring();
		}
	},

	onShow : function() {
		if (this.monitorValid) {
			this.startMonitoring();
		}
		Ext.ux.grid.RowEditor.superclass.onShow.apply(this, arguments);
	},

	onHide : function() {
		Ext.ux.grid.RowEditor.superclass.onHide.apply(this, arguments);
		this.stopMonitoring();
		this.grid.getView().focusRow(this.rowIndex);
	},

	positionButtons : function() {
		if (this.btns) {
			var h = this.el.dom.clientHeight;
			var view = this.grid.getView();
			var scroll = view.scroller.dom.scrollLeft;
			var width = view.mainBody.getWidth();
			var bw = this.btns.getWidth();
			this.btns.el.shift({
						left : (width / 2) - (bw / 2) + scroll,
						top : h - 2,
						stopFx : true,
						duration : 0.2
					});
		}
	},

	// private
	preEditValue : function(r, field) {
		var value = r.data[field];
		return this.autoEncode && typeof value === 'string' ? Ext.util.Format
				.htmlDecode(value) : value;
	},

	// private
	postEditValue : function(value, originalValue, r, field) {
		return this.autoEncode && typeof value == 'string' ? Ext.util.Format
				.htmlEncode(value) : value;
	},

	doFocus : function(pt) {
		if (this.isVisible()) {
			var index = 0;
			if (pt) {
				index = this.getTargetColumnIndex(pt);
			}
			var cm = this.grid.getColumnModel();
			for (var i = index || 0, len = cm.getColumnCount(); i < len; i++) {
				var c = cm.getColumnAt(i);
				if (!c.hidden && c.getEditor()) {
					c.getEditor().focus();
					break;
				}
			}
		}
	},

	getTargetColumnIndex : function(pt) {
		var grid = this.grid, v = grid.view;
		var x = pt.left;
		var cms = grid.colModel.config;
		var i = 0, match = false;
		for (var len = cms.length, c; c = cms[i]; i++) {
			if (!c.hidden) {
				if (Ext.fly(v.getHeaderCell(i)).getRegion().right >= x) {
					match = i;
					break;
				}
			}
		}
		return match;
	},

	startMonitoring : function() {
		if (!this.bound && this.monitorValid) {
			this.bound = true;
			Ext.TaskMgr.start({
						run : this.bindHandler,
						interval : this.monitorPoll || 200,
						scope : this
					});
		}
	},

	stopMonitoring : function() {
		this.bound = false;
		if (this.tooltip) {
			this.tooltip.hide();
		}
	},

	isValid : function() {
		var valid = true;
		this.items.each(function(f) {
					if (!f.isValid(true)) {
						valid = false;
						return false;
					}
				});
		return valid;
	},

	// private
	bindHandler : function() {
		if (!this.bound) {
			return false; // stops binding
		}
		var valid = this.isValid();
		if (!valid && this.errorSummary) {
			this.showTooltip(this.getErrorText().join(''));
		}
		this.btns.saveBtn.setDisabled(!valid);
		this.fireEvent('validation', this, valid);
	},

	showTooltip : function(msg) {
		var t = this.tooltip;
		if (!t) {
			t = this.tooltip = new Ext.ToolTip({
						maxWidth : 600,
						cls : 'errorTip',
						width : 300,
						title : 'Errors',
						autoHide : false,
						anchor : 'left',
						anchorToTarget : true,
						mouseOffset : [40, 0]
					});
		}
		t.initTarget(this.items.last().getEl());
		if (!t.rendered) {
			t.show();
			t.hide();
		}
		t.body.update(msg);
		t.doAutoWidth();
		t.show();
	},

	getErrorText : function() {
		var data = ['<ul>'];
		this.items.each(function(f) {
					if (!f.isValid(true)) {
						data.push('<li>', f.activeError, '</li>');
					}
				});
		data.push('</ul>');
		return data;
	}
});
Ext.preg('roweditor', Ext.ux.grid.RowEditor);

Ext.override(Ext.form.Field, {
			markInvalid : function(msg) {
				if (!this.rendered || this.preventMark) { // not rendered
					return;
				}
				msg = msg || this.invalidText;

				var mt = this.getMessageHandler();
				if (mt) {
					mt.mark(this, msg);
				} else if (this.msgTarget) {
					this.el.addClass(this.invalidClass);
					var t = Ext.getDom(this.msgTarget);
					if (t) {
						t.innerHTML = msg;
						t.style.display = this.msgDisplay;
					}
				}
				this.activeError = msg;
				this.fireEvent('invalid', this, msg);
			}
		});

Ext.override(Ext.ToolTip, {
			doAutoWidth : function() {
				var bw = this.body.getTextWidth();
				if (this.title) {
					bw = Math.max(bw, this.header.child('span')
									.getTextWidth(this.title));
				}
				bw += this.getFrameWidth() + (this.closable ? 20 : 0)
						+ this.body.getPadding("lr") + 20;
				this.setWidth(bw.constrain(this.minWidth, this.maxWidth));

				// IE7 repaint bug on initial show
				if (Ext.isIE7 && !this.repainted) {
					this.el.repaint();
					this.repainted = true;
				}
			}
		});
